use crate::AppResult;
use actix_web::error::ErrorInternalServerError;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use serde_json::from_reader;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use wither::mongodb::options::ServerAddress;
mod default_config {
    use super::*;
    pub fn mongo_host() -> String {
        "localhost".to_string()
    }
    pub fn mongo_port() -> u16 {
        27017
    }
    pub fn mongo_connections() -> Vec<MongodbConnectionConfig> {
        vec![MongodbConnectionConfig::default()]
    }
    pub fn mongo_username() -> String {
        "root".to_string()
    }
    pub fn mongo_password() -> String {
        "root".to_string()
    }
    pub fn mongo_db() -> String {
        "club".to_string()
    }
    pub fn app_name() -> String {
        "shimmer_club".to_string()
    }
    pub fn app_base_path() -> String {
        "http://localhost3000".to_string()
    }
    pub fn server_port() -> u16 {
        3000
    }
    pub fn server_host() -> String {
        "127.0.0.1".to_string()
    }
    pub fn env() -> ConfigEnv {
        ConfigEnv::Dev
    }
    pub fn redis_host() -> String {
        "127.0.0.1".to_string()
    }
    pub fn redis_port() -> u16 {
        6379
    }
    pub fn cookie_key() -> String {
        "redis".to_string()
    }
    pub fn cookie_http_only() -> bool {
        true
    }
    pub fn cookie_max_age() -> i64 {
        60 * 60 * 12
    }
    pub fn cookie_name() -> String {
        "club-session".to_string()
    }
    pub fn cookie_secure() -> bool {
        false
    }
    pub fn session_namespace() -> String {
        "session".to_string()
    }
    pub fn default_club_favicon() -> String {
        "".to_string()
    }
    pub fn upload_base_path() -> String {
        "./uploads".to_string()
    }
    pub fn upload_web_base_path() -> String {
        "/upload".to_string()
    }
    pub fn wechat_token_expires() -> i64 {
        3600
    }
    pub fn session_gc_interval() -> i64 {
        3600
    }
    pub fn wechat_token_task_enable() -> bool {
        true
    }
    pub fn wechat_token_task_interval() -> i64 {
        1600
    }
    pub fn gitee_scope() -> Vec<String> {
        vec!["user_info".to_string()]
    }
    pub fn cache_sync_interval() -> i64 {
        60
    }
    #[inline]
    pub fn email_config() -> Option<EmailConfig> {
        Some(EmailConfig::default())
    }
}
#[derive(Debug, PartialOrd, PartialEq, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConfigEnv {
    Dev,
    Test,
    Pre,
    Prod,
}
impl Default for ConfigEnv {
    fn default() -> Self {
        Self::Dev
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default = "Default::default")]
pub struct MongodbConnectionConfig {
    pub host: String,
    pub port: u16,
}

impl Default for MongodbConnectionConfig {
    fn default() -> Self {
        Self {
            host: default_config::mongo_host(),
            port: default_config::mongo_port(),
        }
    }
}
impl From<MongodbConnectionConfig> for ServerAddress {
    fn from(s: MongodbConnectionConfig) -> Self {
        Self::Tcp {
            port: Some(s.port),
            host: s.host,
        }
    }
}
impl From<&MongodbConnectionConfig> for ServerAddress {
    fn from(m: &MongodbConnectionConfig) -> Self {
        Self::Tcp {
            port: Some(m.port),
            host: m.host.clone(),
        }
    }
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(default = "Default::default")]
pub struct MongodbTLSConfig {
    /// Whether or not the `Client` should return an error if the server
    /// presents an invalid certificate. This setting should _not_ be set to `true` in
    /// production; it should only be used for testing.
    ///
    /// The default value is to error when the server presents an invalid certificate.
    pub allow_invalid_certificates: Option<bool>,

    /// The path to the CA file that the `Client` should use for TLS. If
    /// none is specified, then the driver will use the Mozilla root certificates from the
    /// `webpki-roots` crate.
    pub ca_file_path: Option<String>,

    /// The path to the certificate file that the `Client` should present
    /// to the server to verify its identify. If none is specified, then the
    /// `Client` will not attempt to verify its identity to the
    /// server.
    pub cert_key_file_path: Option<String>,
}

impl From<MongodbTLSConfig> for wither::mongodb::options::TlsOptions {
    fn from(s: MongodbTLSConfig) -> Self {
        wither::mongodb::options::TlsOptions::builder()
            .allow_invalid_certificates(s.allow_invalid_certificates)
            .ca_file_path(s.ca_file_path.map(PathBuf::from))
            .cert_key_file_path(s.cert_key_file_path.map(PathBuf::from))
            .build()
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default = "Default::default")]
pub struct MongodbConfig {
    pub connections: Vec<MongodbConnectionConfig>,
    pub repl_set_name: Option<String>,
    pub username: String,
    pub password: String,
    pub db: String,
    pub tls: Option<MongodbTLSConfig>,
}

impl Default for MongodbConfig {
    fn default() -> Self {
        Self {
            connections: default_config::mongo_connections(),
            repl_set_name: None,
            username: default_config::mongo_username(),
            password: default_config::mongo_password(),
            db: default_config::mongo_db(),
            tls: None,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default = "Default::default")]
pub struct AppConfig {
    pub name: String,
    pub base_path: String,
    pub default_club_favicon: String,
    pub allow_register: bool,
    pub enable_email_validation: bool,
}

impl Default for AppConfig {
    fn default() -> Self {
        Self {
            name: default_config::app_name(),
            base_path: default_config::app_base_path(),
            default_club_favicon: default_config::default_club_favicon(),
            allow_register: true,
            enable_email_validation: false,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default = "Default::default")]
pub struct RedisConfig {
    pub host: String,
    pub port: u16,
}

impl RedisConfig {
    pub fn into_url(self) -> String {
        format!("{}:{}", self.host, self.port)
    }
}

impl Default for RedisConfig {
    fn default() -> Self {
        Self {
            host: default_config::redis_host(),
            port: default_config::redis_port(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default = "Default::default")]
pub struct CookieConfig {
    pub http_only: bool,
    pub max_age: i64,
    pub name: String,
    pub secure: bool,
    pub key: String,
    pub namespace: String,
}
impl Default for CookieConfig {
    fn default() -> Self {
        Self {
            http_only: default_config::cookie_http_only(),
            max_age: default_config::cookie_max_age(),
            name: default_config::cookie_name(),
            secure: default_config::cookie_secure(),
            key: default_config::cookie_key(),
            namespace: default_config::session_namespace(),
        }
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default = "Default::default")]
pub struct ServerConfig {
    pub port: u16,
    pub host: String,
}
impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            port: default_config::server_port(),
            host: default_config::server_host(),
        }
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default = "Default::default")]
pub struct UploadConfig {
    #[serde(default = "default_config::upload_base_path")]
    pub upload_base_path: String,
    #[serde(default = "default_config::upload_web_base_path")]
    pub web_base_path: String,
}

impl Default for UploadConfig {
    fn default() -> Self {
        Self {
            upload_base_path: default_config::upload_base_path(),
            web_base_path: default_config::upload_web_base_path(),
        }
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default = "Default::default")]
pub struct WechatConfig {
    #[serde(default = "default_config::wechat_token_expires")]
    pub web_token_expires: i64,
    pub app_id: Option<String>,
    pub app_secret: Option<String>,
}
impl Default for WechatConfig {
    fn default() -> Self {
        Self {
            web_token_expires: default_config::wechat_token_expires(),
            app_id: None,
            app_secret: None,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default = "Default::default")]
pub struct SessionGCConfig {
    pub interval: i64,
}
impl Default for SessionGCConfig {
    fn default() -> Self {
        Self {
            interval: default_config::session_gc_interval(),
        }
    }
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default = "Default::default")]
pub struct WechatTokenTaskConfig {
    #[serde(default = "default_config::wechat_token_task_enable")]
    pub enable: bool,
    #[serde(default = "default_config::wechat_token_task_interval")]
    pub interval: i64,
}
impl Default for WechatTokenTaskConfig {
    fn default() -> Self {
        Self {
            enable: default_config::wechat_token_task_enable(),
            interval: default_config::wechat_token_task_interval(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default = "Default::default")]
pub struct DailyClearConfig {
    pub enable: bool,
}

impl Default for DailyClearConfig {
    fn default() -> Self {
        Self { enable: true }
    }
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default = "Default::default")]
pub struct TasksConfig {
    /// 清理失效的session任务
    pub session_gc: SessionGCConfig,
    pub wechat_token: WechatTokenTaskConfig,
    /// 缓存同步任务
    pub cache_sync: CacheSyncConfig,
    /// 每日清理任务
    pub daily_clear: DailyClearConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Default)]
#[serde(default = "Default::default")]
pub struct GtihubConfig {
    pub client_id: Option<String>,
    pub client_secret: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(default = "Default::default")]
pub struct GiteeConfig {
    pub client_id: Option<String>,
    pub client_secret: Option<String>,
    pub scope: Vec<String>,
}
impl Default for GiteeConfig {
    fn default() -> Self {
        Self {
            client_id: None,
            client_secret: None,
            scope: default_config::gitee_scope(),
        }
    }
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct CacheSyncNode {
    pub address: String,
    pub port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(default = "Default::default")]
pub struct CacheSyncConfig {
    /// 同步间隔
    #[serde(default = "default_config::cache_sync_interval")]
    pub sync_interval: i64,
}
impl Default for CacheSyncConfig {
    fn default() -> Self {
        Self {
            sync_interval: default_config::cache_sync_interval(),
        }
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailCredentialsConfig {
    pub username: String,
    pub password: String,
}
impl From<EmailCredentialsConfig> for lettre::transport::smtp::authentication::Credentials {
    fn from(e: EmailCredentialsConfig) -> Self {
        Self::new(e.username, e.password)
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailServerConfig {
    pub address: String,
    pub port: Option<u16>,
}
impl Default for EmailServerConfig {
    fn default() -> Self {
        Self {
            address: "smtp.localhost.com".to_string(),
            port: None,
        }
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailConfig {
    pub from: String,
    pub sender: String,
    pub subject: String,
    pub body: String,
    pub credentials: Option<EmailCredentialsConfig>,
    pub server: EmailServerConfig,
}
impl Default for EmailConfig {
    fn default() -> Self {
        Self {
            from: "noreply@localhost.com".to_string(),
            sender: "noreply@localhost.com".to_string(),
            subject: "您的注册验证码".to_string(),
            body: "您的注册验证码为:{{VALID_CODE}}".to_string(),
            credentials: None,
            server: Default::default(),
        }
    }
}
impl EmailConfig {
    pub fn build_message<F: FnOnce(&str) -> String>(
        &self,
        to: lettre::message::Mailbox,
        body_filter: F,
    ) -> AppResult<lettre::Message> {
        let message = lettre::Message::builder()
            .to(to)
            .from(self.from.parse().map_err(ErrorInternalServerError)?)
            .sender(self.sender.parse().map_err(ErrorInternalServerError)?)
            .subject(self.subject.as_str())
            .body(body_filter(self.body.as_str()))
            .map_err(ErrorInternalServerError)?;
        Ok(message)
    }
    pub fn build_mailer<T: lettre::Executor>(&self) -> AppResult<lettre::AsyncSmtpTransport<T>> {
        let mut builder = lettre::AsyncSmtpTransport::<T>::relay(self.server.address.as_str())
            .map_err(ErrorInternalServerError)?;
        if let Some(port) = self.server.port {
            builder = builder.port(port);
        }
        if let Some(ref credentials) = self.credentials {
            builder = builder.credentials(credentials.clone().into());
        }
        Ok(builder.build())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default = "Default::default")]
pub struct Config {
    pub app: AppConfig,
    pub mongo: MongodbConfig,
    pub server: ServerConfig,
    #[serde(default = "default_config::env")]
    pub env: ConfigEnv,
    pub redis: RedisConfig,
    pub cookie: CookieConfig,
    #[serde(default = "Default::default")]
    pub upload: UploadConfig,
    #[serde(default = "Default::default")]
    pub wechat: WechatConfig,
    #[serde(default = "Default::default")]
    pub tasks: TasksConfig,
    #[serde(default = "Default::default")]
    pub github: GtihubConfig,
    #[serde(default = "Default::default")]
    pub gitee: GiteeConfig,
    #[serde(default = "default_config::email_config")]
    pub email: Option<EmailConfig>,
}

impl Config {
    pub fn from_file<T: AsRef<Path>>(path: T) -> Self {
        from_reader(File::open(path).unwrap()).unwrap()
    }

    pub fn try_from_file<T: AsRef<Path>>(path: T) -> AppResult<Self> {
        Ok(from_reader(File::open(path)?)?)
    }
}

pub static GLOBAL_CONFIG: Lazy<RwLock<Option<Arc<Config>>>> = Lazy::new(|| RwLock::new(None));

#[cfg(test)]
mod test {
    use super::*;

    #[derive(Debug, Clone, Serialize, Deserialize, Default)]
    struct B {
        enable: bool,
    }
    #[derive(Debug, Clone, Serialize, Deserialize, Default)]
    #[serde(default = "Default::default")]
    struct A {
        pub name: String,
        pub b: B,
    }

    #[test]
    fn test_deserize() {
        let a_str = "{\"name\":\"asd\"}";
        let a: A = serde_json::from_str(a_str).unwrap();
        println!("a:{:?}", a);
    }
}
